//--------------------------------------------------------------------------------------------------
//                                                   ___  __  __  __  __  __  __  __    __  __  ___ 
// Copyright 2010, Koert van der Veer.              / __)(  )(  )(  \/  )(  )(  )(  )  (  )(  )/ __)
// Licensed under the Apache License,              ( (__  )(__)(  )    (  )(__)(  )(__  )(__)( \__ \
// version 2.0; ("the License")                     \___)(______)(_/\/\_)(______)(____)(______)(___/

// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
//     http://www.apache.org/licenses/LICENSE-2.0 
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License. 
//--------------------------------------------------------------------------------------------------
// This file is part of cumulus,  an memory manager implementation in C++.  Details are discussed in
// my blog,  which can be found at http://ondergetekende.nl.  Comments,  suggestions and bug reports
// are welcome. They can be added as comments at the blog. Be advised that in its current form, this
// code is most likely not production ready; it simply hasn't been tested sufficiently. 
//--------------------------------------------------------------------------------------------------
// Check: a layer that verifies many pre-conditions and post-conditions
//--------------------------------------------------------------------------------------------------

#pragma once

#include "misc.h"

namespace Cumulus 
{

template< typename InnerLayer >
class Check : public InnerLayer
{
	// Static assertions
	static CompileTimeAssert< (InnerLayer::MinAlignment & (InnerLayer::MinAlignment-1)) == 0 > AlignmentShouldBePowerOfTwo;
	static CompileTimeAssert< (InnerLayer::MinAlignment > 0) > AlignmentShouldBePosivive;
	static CompileTimeAssert< (InnerLayer::MinSize > 0) > MinSizeShouldBePosivive;

	// Static variables

	// Magic value used for the Initialized variable, to reduce the chance of false positives.
	// The value was chosen to contain only odd bytes values, because odd values are less common in 
	// memory, due to alignment.
	static const int InitializeMagic = 0x3981EBC1;

public:
	Check();
	~Check();

	/// Returns a pointer to the a new piece of memory, of at least Size.
	/// After the call, Size may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param  Size	in/out The requested size of the new memory allocation. Must be > 0. 
	///					Post: the size usable for writes, larger than
	/// @return			A pointer to the new memory block, or NULL if the allocation was
	/// 				not successful.
	void* Malloc( size_t& Size );

	/// Returns ownership of a block of memory to the memory manager.        
	/// @param  Memory	Pointer to the allocation be released. Must be a valid pointer, as 
	//					previously returned by Malloc. (NULL is invalid)
	void Free( void* Memory );

	/// Returns the client usable size for the provided pointer
	/// This is guaranteed to be equal or larger than the request size provided to Malloc.
	/// Successive calls to GetSize will return the same value, unless Resize was called earlier.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	size_t GetSize( void* Memory );

	/// Tries to change the size of the allocated block to at least NewSize.
	/// After the call, NewSize may be changed to a larger number. In that case,
	/// the caller may use up Size bytes. Note that successive calls to GetSize
	/// may return a different (larger) number.
	/// @param Memory	Pointer to allocation to be modified. Must be a valid pointer, as previously
	///					returned by Malloc. Any other pointer (including NULL) is invalid.
	/// @param NewSize	in/out The new size for the allocation
	/// @return			TRUE if the resize operation was successful.
	bool Resize( void* Memory, size_t& NewSize );

	/// Enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the first Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetNext( void* Memory );

	/// Reverse enumerator for memory allocations.
	/// @param Memory	Pointer to allocation queried. Must be a valid pointer, as previously 
	///					returned by Malloc, or NULL to request the last Allocation.
	/// @return			A pointer to the memory allocation after Memory.
	void* GetPrevious( void* Memory );

private:
	// Simple helper which guards reentrance based on scope.
	// Just construct an ScopeReentranceGuard, and it will guarantee
	struct ScopeReentranceGuard
	{
		ScopeReentranceGuard( volatile LONG * InReentranceGuard );
		~ScopeReentranceGuard();

	private:
		volatile LONG * ReentranceGuard; 
		bool DidEnter;
	};

	
	// Guard to guarantee this layer is only accessed if it is also initialized. This is initialized
	// to InitializeMagic in the constructor, and deinitialized in the destructor.
	int Initialized;

	// NULL, or the Thread id of the current thread accessing this layer.
	volatile LONG CurrentAccessThread;

	bool IsInitialized() { return InitializeMagic == Initialized; }
	bool CouldBeValidPointer( void* Memory, bool NullIsValid );
};

// -------------------------------------------------------------------------------------------------

template< typename InnerLayer >
Check<InnerLayer>::Check()
: CurrentAccessThread( NULL )
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.

	assert( !IsInitialized() );
	Initialized = InitializeMagic;
}

template< typename InnerLayer >
Check<InnerLayer>::~Check()
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.
	assert( IsInitialized() );	
	Initialized = 0;
}


// Returns a pointer to the a new piece of memory, of at least Size.
template< typename InnerLayer >
void* Check<InnerLayer>::Malloc( size_t& Size ) 
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.
	assert( IsInitialized() );	

	// The size parameter might be changed by the inner Malloc call, so back up the initial value.
	size_t RequestedSize = Size;

	// Zero sized allocations are not supported.
	assert( Size > 0 ); 

	void* Result = InnerLayer::Malloc( Size );

	if( Result != NULL )
	{
		assert( CouldBeValidPointer( Result, false ) );

		// Make sure we got at least the size we asked for
		assert( InnerLayer::GetSize( Result ) >= Size );
		assert( Size >= RequestedSize );

		// Make sure we got at least the size that's advertised
		assert( InnerLayer::GetSize( Result ) >= InnerLayer::MinSize );
	}

	return Result; 
}

// Returns ownership of a block of memory to the memory manager.
template< typename InnerLayer >
void Check<InnerLayer>::Free( void* Memory ) 
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.

	assert( IsInitialized() );	
	assert( CouldBeValidPointer( Memory, false ) );

	InnerLayer::Free( Memory );
}

// Returns the client usable size for the provided pointer
template< typename InnerLayer >
size_t Check<InnerLayer>::GetSize( void* Memory )
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.
	assert( IsInitialized() );	

	assert( CouldBeValidPointer( Memory, false ) );

	size_t Result = InnerLayer::GetSize( Memory );

	assert( Result >= InnerLayer::MinSize );

	return Result;
}

// Tries to change the size of the allocated block to at least NewSize.
// If the request can be satisfied, the new usable size is returned,
// otherwise 0 is returned.
template< typename InnerLayer >
bool Check<InnerLayer>::Resize( void* Memory, size_t& NewSize )
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.
	assert( IsInitialized() );	

	assert( CouldBeValidPointer( Memory, false ) );

	// Don't allow empty allocations
	assert( NewSize > 0 );

	// Resize can change the Size parameter, so make a backup here.
	size_t RequestedSize = NewSize;

	bool Result = InnerLayer::Resize( Memory, NewSize );

	if( Result )
	{
		// make sure we got what we asked
		assert( NewSize >= RequestedSize );

		// make sure the room is actually available
		assert( GetSize(Memory) >= NewSize );

		assert( NewSize >= InnerLayer::MinSize );
	}

	return Result;
}

template< typename InnerLayer >
void* Check<InnerLayer>::GetNext( void* Memory )
{
	ScopeReentranceGuard Guard( &CurrentAccessThread ); // Enforce reentrance rules.
	assert( IsInitialized() );	

	assert( CouldBeValidPointer( Memory, true ) );

	void* Result = InnerLayer::GetNext( Memory ); 

	assert( CouldBeValidPointer( Result, true ) );

	return Result;
}

template< typename InnerLayer >
void* Check<InnerLayer>::GetPrevious( void* Memory )
{
	ScopeReentranceGuard Guard( &CurrentAccessThread );

	assert( CouldBeValidPointer( Memory, true ) );

	void* Result = return InnerLayer::GetPrevious( Memory ); 

	assert( CouldBeValidPointer( Result, true ) );

	return Result;
}

template< typename InnerLayer >
bool Check<InnerLayer>::CouldBeValidPointer( void* Memory, bool NullIsValid )
{
	bool IsValid = true;

	if( Memory != NULL )
	{
		IsValid = NullIsValid; 
	}
	else
	{
		int AddressInt = (int) Memory;

		IsValid &= (AddressInt & (InnerLayer::MinAlignment - 1)) == 0;
	}

	return IsValid;
}


template< typename InnerLayer >
Check<InnerLayer>::ScopeReentranceGuard::ScopeReentranceGuard( volatile LONG * InReentranceGuard )
	: ReentranceGuard( InReentranceGuard )
{
	// Only perform scope checks if the inner layer advertizes to be thread safe.
	if( InnerLayer::IsThreadSafe )
	{
		DWORD PreviousThreadId = InterlockedExchange( ReentranceGuard, GetCurrentThreadId() );
		DidEnter = PreviousThreadId == NULL;
		assert( PreviousThreadId == NULL || PreviousThreadId == GetCurrentThreadId() );
	}
}

template< typename InnerLayer>
Check<InnerLayer>::ScopeReentranceGuard::~ScopeReentranceGuard()
{
	// Only perform scope checks if the inner layer advertises to be thread safe.
	if( InnerLayer::IsThreadSafe )
	{
		assert( *ReentranceGuard == GetCurrentThreadId() );

		if( DidEnter )
		{ 
			InterlockedExchange( ReentranceGuard, NULL );
		}
	}
}

}